﻿module ComputationExpression

open Xunit
open FsUnit.Xunit

[<Fact>]
let ``log without computation expression : pain``()=
    let log p = printfn "expression is %A" p

    let loggedWorkflow = 
        let x = 42
        log x
        let y = 43
        log y
        let z = x + y
        log z
        //return
        z

    loggedWorkflow

type LoggingBuilder() = 
    let log p = printfn "expression with bind is %A" p

    member this.Bind(x, f) =
        log x
        f x

    member this.Return(x) = 
        x

[<Fact>]
let ``log with bind and return``()=
    let logger = new LoggingBuilder()

    logger{
        let! x = 42
        let! y = 43
        let! z = x + y
        return z
    }

let mutable counter = 0

[<Fact>]
let ``Increment counter with mutable (bad practice)``()=

    let DoSomething() =
        printfn "DoSomething. Counter=%i" counter

    let FinalResult() =
        printfn "FinalResult. Counter=%i" counter

    let counterWorkFlow =
        do DoSomething()
        counter <- counter + 1
        do DoSomething()
        counter <- counter + 1
        do DoSomething()
        counter <- counter + 1
        do FinalResult()

    counterWorkFlow

[<Fact>]
let ``Increment counter with by injecting state``()=
    let DoSomething counter = 
        printfn "DoSomething. Counter=%i" counter
        counter + 1

    let FinalResult counter = 
        printfn "FinalResult. Counter=%i" counter

    let counterWorkFlow = 
        let counter = 0

        let counter' = DoSomething counter
        let counter'' = DoSomething counter'
        let counter''' = DoSomething counter''
        do FinalResult counter'''

    counterWorkFlow

type State<'a, 's> = State of ('s -> 'a* 's)

let runState (State s) a = s a
let getState = State (fun s -> (s,s))
let putState s = State (fun _ -> ((),s))

type StateBuilder() =
    member this.Return(a) =
        State (fun s -> (a,s))
    member this.Bind(m,k) = 
        State (fun s ->
            let (a,s') = runState m s
            runState (k a) s')
    member this.ReturnFrom(m) = m

[<Fact>]
let ``Increment with StateBuilder``()=

    let state = new StateBuilder()

    let DoSomething counter = 
        printfn "DoSomething. Counter=%i" counter
        counter + 1

    let FinalResult counter = 
        printfn "FinalResult. Counter=%i" counter
        counter

    let lift f = state{
        let! s = getState
        return! putState (f s)
        }

    let DoSomething' = lift DoSomething
    let FinalResult' = lift FinalResult

    let counterWorkflow = 
        let s = state {
            do! DoSomething'
            do! DoSomething'
            do! DoSomething'
            do! FinalResult'
            } 
        runState s 0

    counterWorkflow